package com.shujia

import org.junit.{Before, Test}

import scala.io.{BufferedSource, Source}

class Demo19StudentExam {
  // 定义全局变量 让每一个方法中都能使用
  var stuList: List[Student] = _
  var scoreList: List[Score] = _
  var subjectList: List[Subject] = _

  var sumScoreMap: Map[String, Int] = _

  var stuMap: Map[String, String] = _
  var subMap: Map[String, String] = _

  // 构建科目总分Map，以科目id作为key，科目总分作为Value
  var subFullScoMap: Map[String, Int] = _

  @Before
  // 该方法会在@Test标记的方法运行之前运行
  def init(): Unit = {
    // 加载三类数据并将每一条数据转换成对应的样例类对象的格式 再放入List中
    // 使用JUNIT时默认的工作目录为当前模块的目录，所以这里不需要加载scala/前缀
    val stuBS: BufferedSource = Source.fromFile("data/students.txt")
    val scoreBS: BufferedSource = Source.fromFile("data/score.txt")
    val subjectBS: BufferedSource = Source.fromFile("data/subject.txt")

    stuList = stuBS.getLines().toList.map(line => {
      val splits: Array[String] = line.split(",")
      Student(splits(0), splits(1), splits(2).toInt, splits(3), splits(4))
    })

    scoreList = scoreBS.getLines().toList.map(line => {
      val splits: Array[String] = line.split(",")
      Score(splits(0), splits(1), splits(2).toInt)
    })

    subjectList = subjectBS.getLines().toList.map(line => {
      val splits: Array[String] = line.split(",")
      Subject(splits(0), splits(1), splits(2).toInt)
    })

    sumScoreMap = scoreList
      // 取出id和分数
      .map(sco => {
        (sco.id, sco.score)
      })
      // 按照id进行分组
      .groupBy(t2 => t2._1)
      .map(kv => {
        val id: String = kv._1
        val sumSco: Int = kv._2.map(idAndScore => idAndScore._2).sum
        (id, sumSco)
      })

    // 以id作为Key，name拼接clazz作为Value 构建stuMap
    stuMap = stuList
      .map(stu => {
        (stu.id, s"${stu.name},${stu.clazz}")
      }).toMap

    // 以科目id subject 作为Key，subjectName作为Value 构建subMap
    subMap = subjectList
      .map(sub => {
        (sub.subject, sub.subjectName)
      }).toMap

    // 构建科目总分Map，以科目id作为key，科目总分作为Value
    subFullScoMap = subjectList.map(sub => (sub.subject, sub.fullScore)).toMap

    // 关闭文件
    stuBS.close()
    scoreBS.close()
    subjectBS.close()
  }

  def filterAndPrintWithIds(ids: Set[String]): Unit = {
    // 以分数数据scoList作为中间表（主表）进行遍历
    scoreList
      // 基于前10名学生的id进行过滤
      .filter(sco => ids.contains(sco.id))
      // 逐条处理数据，借助Map 通过Key获取Value这种方式进行关联操作
      .map(sco => {
        // 通过id 从stuMap中获取学生的姓名和班级
        val nameAndClazz: String = stuMap.getOrElse(sco.id, "")
        // 通过subject科目id 从subMap中获取科目的名称
        val subjectName: String = subMap.getOrElse(sco.subject, "")
        s"${sco.id},$nameAndClazz,$subjectName,${sco.score}"
      })
      .foreach(println)
  }

  @Test
  // 基础题：2、统计学生的总分 [学号,学生姓名,学生年龄,总分]
  def stuSumScore(): Unit = {
    // 计算总分
    //    val sumScoMap: Map[String, Int] = scoreList
    //      // 取出id和分数
    //      .map(sco => {
    //        (sco.id, sco.score)
    //      })
    //      // 按照id进行分组
    //      .groupBy(t2 => t2._1)
    //      .map(kv => {
    //        val id: String = kv._1
    //        val sumSco: Int = kv._2.map(idAndScore => idAndScore._2).sum
    //        (id, sumSco)
    //      })
    //      .toMap // 如果在groupBy之后使用map方法，在map方法中如果返回的是二元组 则toMap可以省略

    // 遍历学生信息数据
    stuList
      .map(stu => {
        val sumScore: Int = sumScoreMap.getOrElse(stu.id, 0)
        s"${stu.id},${stu.name},${stu.age},$sumScore"
      })
      .foreach(println)
  }

  @Test
  // 1、统计年级排名前十学生各科的分数 [学号,姓名，班级，科目，分数]
  def question01(): Unit = {
    // 计算学生总分 取出前十名学生的id
    val top10Ids: Set[String] = sumScoreMap
      // 按照学生总分降序排列
      .toList // Map没有sortBy排序方法，需要先转成List
      .sortBy(-_._2) // 指定按照总分进行降序排序
      .take(10) // 取前10条数据
      .map(_._1) // 提取id
      .toSet

    filterAndPrintWithIds(top10Ids)


  }

  @Test
  // 2、统计总分大于年级平均分的学生 [学号，姓名，班级，总分]
  def question02(): Unit = {
    // 计算年级平均分 372.704
    val avgScore: Double = sumScoreMap.values.sum / sumScoreMap.size.toDouble

    // 过滤出总分大于年级平均分的学生
    sumScoreMap
      .filter(_._2 > avgScore)
      // 通过id 获取学生的name以及clazz
      .map(kv => {
        val id: String = kv._1
        val sumScore: Int = kv._2
        val nameAndClazz: String = stuMap.getOrElse(kv._1, "")
        s"$id,$nameAndClazz,$sumScore"
      }).foreach(println)


  }

  @Test
  // 3、统计每科都及格的学生 [学号，姓名，班级，科目，分数]
  def question03(): Unit = {
    // 关联分数以及科目数据
    val ids: Set[String] = scoreList
      // 将每个学生及格的科目过滤出来
      .filter(sco => {
        val fullScore: Int = subFullScoMap.getOrElse(sco.subject, 0)
        sco.score >= fullScore * 0.6
      })
      // 统计每个学生及格的科目数
      .groupBy(sco => sco.id)
      .map(kv => (kv._1, kv._2.size))
      // 过滤出及格的科目数为6 即表示所有科目都及格
      .filter(_._2 == 6)
      // 提取id
      .keys
      .toSet

    filterAndPrintWithIds(ids)

  }

  @Test
  // 4、统计每个班级的前三名 [学号，姓名，班级，分数]
  def question04(): Unit = {
    sumScoreMap // 学生总分
      // 关联 stuMap 通过id获取学生的name以及clazz
      .map(kv => {
        val id: String = kv._1
        val nameAndClazz: String = stuMap.getOrElse(id, "")
        val splits: Array[String] = nameAndClazz.split(",")
        (id, splits(0), splits(1), kv._2)
      })
      // 将同一个班的数据进行分组处理
      .groupBy(_._3)
      .flatMap(kv => {
        // 按照总分降序排序 取前三名
        kv._2.toList.sortBy(-_._4).take(3).map(t4 => s"${t4._1},${t4._2},${t4._3},${t4._4}")
      }).foreach(println)
  }

  @Test
  // 5、统计偏科最严重的前100名学生  [学号，姓名，班级，科目，分数]
  def question05(): Unit = {
    val ids: Set[String] = scoreList
      // 因为科目的总分不一致 所需需要先通过科目id关联获取科目总分，将所有成绩转换成百分制再进行处理
      .map(sco => {
        val fullScore: Int = subFullScoMap.getOrElse(sco.subject, 100)
        (sco.id, sco.score * 100.toDouble / fullScore)
      })
      // 按照id分组，将每个学生的6门科目成绩放在一组进行处理
      .groupBy(_._1)
      // 分别计算每个学生的平均分以及方差
      .map(kv => {
        val id: String = kv._1
        // 计算平均分
        val avgScore: Double = kv._2.map(_._2).sum / kv._2.size
        // 计算方差
        val variance: Double = kv._2.map(t2 => Math.pow(t2._2 - avgScore, 2)).sum / kv._2.size
        (id, variance)
      })
      .toList
      .sortBy(-_._2)
      .take(100)
      .map(_._1)
      .toSet

    filterAndPrintWithIds(ids)

  }

}

case class Student(id: String, name: String, age: Int, gender: String, clazz: String)

case class Score(id: String, subject: String, score: Int)

case class Subject(subject: String, subjectName: String, fullScore: Int)
